【Laravel + Vue】出張先を想定してオフライン対応にする

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、私の家はどういうわけかネットの通信状態が良かったこともあり、長らくADSLを使っていたのですが、少し前にそのサービス自体が終了になるということで、仕方なく光回線へ移行しました。(携帯料金より、こっちの方が安くなってほしい…😫)

そして、その工事をしているときに作業員さんがタブレットをもっていたのですが、そのとき思ったのが、

「あれって、オフラインになっても大丈夫なのかな??」

というものでした。

そうです。
「ネットをつなぎに来る人」にも「ネットがつながらなくなるときがある」のは当然の話だと思ったんですね。

そこで❗

今回は、Laravel + Vueを使って「オフライン対応にする」を実装してみることにしました。

ぜひ何かの参考になりましたら嬉しいです。😄✨

「眉毛カット中に
顔をしかめたら
ジョリってしまった😫」

開発環境: Laravel 9.x、Vue 3

モデル、マイグレーション、Seeder、コントローラーを作成する

では、いきなりですがLaravelで必要なファイルを作成していきます。
以下のコマンドを実行してください。

php artisan make:model Customer -msc

すると、一気に以下のの4ファイルが作成されるので、中身をひとつずつ変更していきましょう。

  • モデル
  • マイグレーション
  • Seeder
  • コントローラー

モデルをつくる

今回モデルは変更なしでOKです(ラク✨)

マイグレーションをつくる

マイグレーション(データベースの設計図)には以下3つのフィールドが追加されるようにします。

  • 名前
  • 電話番号
  • 住所

database/migrations/2022_06_15_181255_create_customers_table.php

// 省略

public function up()
{
    Schema::create('customers', function (Blueprint $table) {
        $table->id();
        $table->string('name')->comment('名前');
        $table->string('tel')->comment('電話番号');
        $table->string('address')->comment('住所');
        $table->timestamps();
    });
}

// 省略

Seeder をつくる

続いてテストデータをつくるためのSeederを作っていきます。
今回はテストなので3人だけ顧客として追加します。

database/seeders/CustomerSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Customer;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class CustomerSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $now = now();
        $customers = [
            [
                'name' => '山田太郎',
                'tel' => '090xxxxxxxx',
                'address' => '東京都 xxx 区 xxx',
                'created_at' => $now,
                'updated_at' => $now,
            ],
            [
                'name' => '佐藤二郎',
                'tel' => '090yyyyyyyy',
                'address' => '東京都 yyy 区 yyy',
                'created_at' => $now,
                'updated_at' => $now,
            ],
            [
                'name' => '田中三郎',
                'tel' => '090zzzzzzzz',
                'address' => '東京都 zzz 区 zzz',
                'created_at' => $now,
                'updated_at' => $now,
            ]
        ];

        Customer::insert($customers);
    }
}

Seederは作成しただけでは有効にならないので、DatabaseSeeder.phpへ登録しておきましょう。

database/seeders/DatabaseSeeder.php

// 省略

class DatabaseSeeder extends Seeder
{
    // 省略

    public function run()
    {
        // 省略

        $this->call(CustomerSeeder::class); // 👈こちら
    }
}

では、この状態でDBを初期化してみます。
以下のコマンドを実行してください。

php artisan migrate:fresh --seed

すると、実際のテーブルはこうなりました。

コントローラーをつくる

app/Http/Controllers/CustomerController.php

<?php

namespace App\Http\Controllers;

use App\Models\Customer;
use Illuminate\Http\Request;

class CustomerController extends Controller
{
    public function index()
    {
        return view('customer.index');
    }

    public function list()
    {
        return Customer::select('id', 'name', 'tel', 'address')->get();
    }
}

中身をみていただくと分かるとおり、とてもシンプルになっていてindex()はブラウザで直接アクセスする部分、list()Ajaxで顧客データを取得する部分という位置づけになってます。

ビュー(オフライン部分)をつくる

では、ここからがメインの「オンライン/オフライン」を管理する部分になります。実行するのは、以下2パターンです。

  • オンラインの場合: サーバーへアクセスし、最新情報を取得する
  • オフラインの場合: ブラウザに保存された情報を使う

実際のコードは以下になります。

resources/views/customer/index.blade.php

<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="p-5">
    <div class="container">
        <div class="row">
            <div class="col-md-12 mb-3">
                <span class="badge rounded-pill bg-danger" v-if="isOnline">オンライン</span>
                <span class="badge rounded-pill bg-light text-dark" v-else>オフライン</span>
            </div>
            <div class="col-md-12">
                <div class="card">
                    <div class="card-header">
                        <h3>顧客リスト</h3>
                    </div>
                    <div class="card-body">
                        <table class="table">
                            <thead>
                            <tr>
                                <th>ID</th>
                                <th>お名前</th>
                                <th>電話番号</th>
                                <th>住所</th>
                            </tr>
                            </thead>
                            <tbody>
                                <tr v-for="customer in customers">
                                    <td v-text="customer.id"></td>
                                    <td v-text="customer.name"></td>
                                    <td v-text="customer.tel"></td>
                                    <td v-text="customer.address"></td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<script src="https://unpkg.com/vue@3.2.31/dist/vue.global.prod.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.26.1/axios.min.js"></script>
<script>

    const { createApp, ref, onMounted } = Vue;

    createApp({
        setup() {

            // Customers
            const customers = ref([]);
            const getCustomers = () => {

                if(isOnline.value === true) { // オンラインの時はサーバーから取得

                    const url = '{{ route('customer.list') }}';
                    axios.get(url)
                        .then(response => {

                            customers.value = response.data;
                            saveCustomers();

                        });

                } else { // オフラインの時は localStorage から取得

                    customers.value = retrieveCustomers();

                }

            };
            const retrieveCustomers = () => { // localStorage から顧客データを取得

                let customers = [];

                try {

                    const json = localStorage.getItem('customers');
                    customers = JSON.parse(json);

                } catch (e) {

                    console.log(e);

                }

                return customers;

            };
            const saveCustomers = () => { // LocalStorage に顧客情報を保存

                const json = JSON.stringify(customers.value);
                localStorage.setItem('customers', json);

            }

            // Online / Offline
            const isOnline = ref(navigator.onLine);

            onMounted(() => {

                window.addEventListener('online', () => { // オンライン時

                    isOnline.value = true;
                    getCustomers();

                });
                window.addEventListener('offline', () => { // オフライン時

                    isOnline.value = false;
                    getCustomers();

                });

                getCustomers();

            });

            return {
                customers,
                isOnline,
            };

        }
    }).mount('#app');

</script>
</body>
</html>

この中で一番重要なのが、以下の「オンライン/オフライン」が切り替わったときに呼ばれるonlineofflineイベントです。

window.addEventListener('online', () => { // オンライン時

    // 省略

});
window.addEventListener('offline', () => { // オフライン時

    // 省略

});

こうすることで、通信状態が変わった時点でいろいろと作業ができるわけですね。

そして、これらのイベントの中で呼ばれているgetCustomers()では、現在の通信状態に応じて、以下2つを切り替えています。

  • サーバー(ネット)からデータ取得する
  • (過去にブラウザに保存した)localStorage からデータ取得する
const getCustomers = () => {

    if(isOnline.value === true) { // オンラインの時はサーバーから取得

        // 省略

    } else { // オフラインの時は localStorage から取得

        // 省略

    }

};

※ つまり、最低でも一回はオンライン状態になって顧客データを取得しておかないといけないということになります。
※ またこのHTML部分はローカルで動いている必要がありますので、本番環境では「PWA化」するなどするといいでしょう。

📝 参考URL: PWAアプリをLaravel + Vueでつくる方法

ルートをつくる

では、最後にルートを作りましょう。

routes/web.php

// 省略

use App\Http\Controllers\CustomerController;

// 省略

Route::prefix('customer')->controller(CustomerController::class)->group(function(){

    Route::get('/', 'index')->name('customer.index');
    Route::get('list', 'list')->name('customer.list');

});

controller()、便利ですね😄👍

テストしてみる

では、実際にテストしてみましょう❗

まずは、オンライン状態のままブラウザで「https://******/customer」へアクセスしてみます。

すると・・・・・・

はい❗
先ほどテストデータとして用意した3人の顧客データが表示され、「オンライン」状態であることが表示されています。

では、次に有線コードを抜いてオフライン状態にしてみます。(環境によって wifi接続を切るなどしてください)

どうなったでしょうか・・・・・・

はい❗
表示がオフラインになっていますが、顧客データはそのままです。

では、この状態のままリロードしています。
うまくlocalStorageからデータを読み込めるでしょうか??

うまくいきました❗

では、最後にオフライン状態のまま、新しい顧客データを追加し、オンラインになった時点で情報が更新されるかをチェックしてみましょう。

この状態で有線コードを接続してみます。

これはうまくいくでしょうか・・・・・・

はい❗❗

オンラインになった瞬間に顧客データが更新され、「山本四郎」さんのデータが追加されました。

また、「オンライン」表示へ変更になっています。

すべて成功です😄✨

企業様へのご提案

今回の技術を使えば、以下のような方々に「オンライン/オフライン」どちらにも対応したシステムをご提供することができます。

  • 外回りをする営業さん
  • 工事などをする技術者さん
  • 通信状態が保証されない海外へ行く経営者さん

もしご興味があるようでしたら、いつでもお気軽に「お問い合わせ」からご連絡ください。

お待ちしております。😄✨

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回は「Laravel + Vue」でオフライン対応をしてみました。

ちなみに今「USJを劇的に変えた、たった1つの考え方」という有名な本を読んでいるんですけど、そこに「日本は技術に偏りすぎて、マーケティングができていない」という内容があり、それを受けて技術一辺倒というわけではない内容にしてみました。

その昔は、「プログラマーなんだからプログラムの技術だけ磨けばいいんでしょ」ぐらいに考えてましたが、まさにそれが日本の現状だったんだ気づかされました。(やっぱ優秀な人はすごいですね😄)

ということで、今後は技術だけじゃなく、マーケティング <-> プログラムという2つを行き来するような考えもできるようになれたらなと考えています。

日々精進が必要ですね(笑)

ではでは〜❗

「天才・オードリータンの本も
買っちゃいました。
衝動買い…😂」

このエントリーをはてなブックマークに追加       follow us in feedly